package com.example.diancai.ai.game2048;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import com.alibaba.fastjson2.JSON;
import com.example.diancai.dto.Vec3DTO;
import com.example.diancai.dto.Vec3StatusDTO;
import com.example.diancai.websocket.dto.UsePropDTO;
import lombok.Data;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

@Data
public class Grid {
    int SIZE = 4;
    int size;
    int startTiles;
    Tile[][] cells;
    public boolean playerTurn;
    public Vec3DTO[][] indexes = new Vec3DTO[4][4];
    private int level;
    public boolean isSearch;
    public LinkedList<UsePropDTO> usePropDTOS = new LinkedList<>();
    private int minSearchTime = 100; // 假设的最小搜索时间


    // 初始化静态属性
    static {

    }

    public void setLevel(int level) {
        this.level = level;
        if (level == 1) {
            this.minSearchTime = 600;
        } else if (level == 2) {
            this.minSearchTime = 400;
        } else if (level == 3) {
            this.minSearchTime = 300;
        } else if (level == 4) {
            this.minSearchTime = 250;
        } else if (level == 5) {
            this.minSearchTime = 200;
        } else if (level == 6) {
            this.minSearchTime = 120;
        }
    }

    public Grid(int size) {
        this.size = size;
        this.startTiles = 2;
        this.cells = new Tile[size][size];
        this.playerTurn = true;
        this.build();
    }

    public void build() {

        for (int x = 0; x < 4; x++) {
            indexes[x] = new Vec3DTO[4];
            for (int y = 0; y < 4; y++) {
                indexes[x][y] = new Vec3DTO(x, y);
            }
        }

        for (int x = 0; x < this.size; x++) {
            for (int y = 0; y < this.size; y++) {
                this.cells[x][y] = null;
            }
        }
    }

    public Vec3DTO randomAvailableCell() {
        List<Vec3DTO> cells = this.availableCells();
        if (cells.size() > 0) {
            int randomIndex = (int) (Math.random() * cells.size());
            return cells.get(randomIndex);
        }
        return null;
    }

    public List<Vec3DTO> availableCells() {
        List<Vec3DTO> cells = new ArrayList<>();
        this.eachCell((x, y, tile) -> {
            if (tile == null) {
                cells.add(new Vec3DTO(x, y));
            }
        });
        return cells;
    }

    public void eachCell(CellCallback callback) {
        for (int x = 0; x < this.size; x++) {
            for (int y = 0; y < this.size; y++) {
                callback.call(x, y, this.cells[x][y]);
            }
        }
    }

    public void insertTile(Tile tile) {
        this.cells[tile.x][tile.y] = tile;
    }

    public void removeTile(Tile tile) {
        this.cells[tile.x][tile.y] = null;
    }

    public void removeTile(Vec3DTO vec3DTO) {
        this.cells[(int) vec3DTO.x][(int) vec3DTO.y] = null;
    }

    public Tile cellContent(Vec3DTO cell) {
        if (this.withinBounds(cell)) {
            return this.cells[(int) cell.x][(int) cell.y];
        }
        return null;
    }

    public boolean cellOccupied(Vec3DTO cell) {
        return this.cellContent(cell) != null;
    }

    public boolean withinBounds(Vec3DTO position) {
        return position.x >= 0 && position.x < this.size &&
                position.y >= 0 && position.y < this.size;
    }

    public boolean cellAvailable(Vec3DTO cell) {
        return !this.cellOccupied(cell);
    }

    public Grid clone() {
        Grid newGrid = new Grid(this.size);
        newGrid.playerTurn = this.playerTurn;
        newGrid.minSearchTime = this.minSearchTime;
        newGrid.usePropDTOS = this.usePropDTOS;
        newGrid.isSearch = this.isSearch;
        newGrid.level = this.level;
        for (int x = 0; x < this.size; x++) {
            for (int y = 0; y < this.size; y++) {
                Tile tile = this.cells[x][y];
                if (tile != null) {
                    newGrid.insertTile(tile.clone());
                }
            }
        }
        return newGrid;
    }

    Vec3DTO[] vectors = {
            new Vec3DTO(0, -1), // up
            new Vec3DTO(1, 0),  // right
            new Vec3DTO(0, 1),  // down
            new Vec3DTO(-1, 0)  // left
    };

    public double smoothness() {
        double smoothness = 0;
        for (int x = 0; x < SIZE; x++) {
            for (int y = 0; y < SIZE; y++) {
                if (this.cellOccupied(indexes[x][y])) {
                    double value = Math.log(this.cellContent(indexes[x][y]).getValue()) / Math.log(2);
                    for (int direction = 1; direction <= 2; direction++) {
                        Vec3DTO vector = this.getVector(direction);
                        Vec3DTO targetCell = this.findFarthestPosition(indexes[x][y], vector).next;

                        if (this.cellOccupied(targetCell)) {
                            Tile target = this.cellContent(targetCell);
                            double targetValue = Math.log(target.getValue()) / Math.log(2);
                            smoothness -= Math.abs(value - targetValue);
                        }
                    }
                }
            }
        }
        return smoothness;
    }

    public FarthestResult findFarthestPosition(Vec3DTO cell, Vec3DTO vector) {
        Vec3DTO previous;

        // Progress towards the vector direction until an obstacle is found
        do {
            previous = cell;
            cell = new Vec3DTO(previous.getX() + vector.getX(), previous.getY() + vector.getY());
        } while (this.withinBounds(cell) && this.cellAvailable(cell));

        return new FarthestResult(previous, cell);
    }

    public double monotonicity2() {
        // scores for all four directions
        double[] totals = {0, 0, 0, 0};

        // up/down direction
        for (int x = 0; x < SIZE; x++) {
            int current = 0;
            int next = current + 1;
            while (next < SIZE) {
                while (next < SIZE && !this.cellOccupied(indexes[x][next])) {
                    next++;
                }
                if (next >= SIZE) {
                    next--;
                }
                double currentValue = this.cellOccupied(new Vec3DTO(x, current)) ?
                        Math.log(this.cellContent(indexes[x][current]).getValue()) / Math.log(2) :
                        0;
                double nextValue = this.cellOccupied(new Vec3DTO(x, next)) ?
                        Math.log(this.cellContent(indexes[x][next]).getValue()) / Math.log(2) :
                        0;
                if (currentValue > nextValue) {
                    totals[0] += nextValue - currentValue;
                } else if (nextValue > currentValue) {
                    totals[1] += currentValue - nextValue;
                }
                current = next;
                next++;
            }
        }

        // left/right direction
        for (int y = 0; y < SIZE; y++) {
            int current = 0;
            int next = current + 1;
            while (next < SIZE) {
                while (next < SIZE && !this.cellOccupied(indexes[next][y])) {
                    next++;
                }
                if (next >= SIZE) {
                    next--;
                }
                double currentValue = this.cellOccupied(new Vec3DTO(current, y)) ?
                        Math.log(this.cellContent(indexes[current][y]).getValue()) / Math.log(2) :
                        0;
                double nextValue = this.cellOccupied(new Vec3DTO(next, y)) ?
                        Math.log(this.cellContent(indexes[next][y]).getValue()) / Math.log(2) :
                        0;
                if (currentValue > nextValue) {
                    totals[2] += nextValue - currentValue;
                } else if (nextValue > currentValue) {
                    totals[3] += currentValue - nextValue;
                }
                current = next;
                next++;
            }
        }

        return Math.max(totals[0], totals[1]) + Math.max(totals[2], totals[3]);
    }

    public int maxValue() {
        final int[] max = {0};
        this.eachCell((x, y, tile) -> {
            if (tile != null && tile.value > max[0]) {
                max[0] = tile.value;
            }
        });
        return max[0];
    }

    public MoveResult move(int direction) {
        // 0: up, 1: right, 2:down, 3: left
        Vec3DTO vector = this.getVector(direction);
        Traversals traversals = this.buildTraversals(vector);
        boolean moved = false;
        int score = 0;
        boolean won = false;

        // Save the current tile positions and remove merger information
        this.prepareTiles();

        // Traverse the grid in the right direction and move tiles
        for (int x : traversals.x) {
            for (int y : traversals.y) {
                Vec3DTO cell = indexes[x][y];
                Tile tile = this.cellContent(cell);

                if (tile != null) {
                    //if (debug) {
                    //console.log('tile @', x, y);
                    //}
                    FarthestResult positions = this.findFarthestPosition(cell, vector);
                    Tile next = this.cellContent(positions.getNext());

                    // Only one merger per row traversal?
                    if (next != null && next.getValue() == tile.getValue() && next.getMergedFrom() == null

                    ) {
                        if ((!isContains(next.status, ListUtil.toList(51, 101)))
                                && (!isContains(tile.status, ListUtil.toList(51, 101)))) {
                            positions.getNext().setZ(tile.getValue() * 2);
                            Tile merged = new Tile(positions.getNext());
                            List<Tile> mergedFrom = new ArrayList<>();
                            mergedFrom.add(tile);
                            mergedFrom.add(next);
                            merged.setMergedFrom(mergedFrom);

                            this.insertTile(merged);
                            this.removeTile(tile);

                            // Converge the two tiles' positions
                            tile.updatePosition(positions.getNext());

                            // Update the score
                            score += merged.getValue();

                            // 查找周边的解冻
                            this.unfreeze(merged);

                            // The mighty 2048 tile
                            if (merged.getValue() == 2048) {
                                won = true;
                            }
                        }

                    } else {
                        //if (debug) {
                        //console.log(cell);
                        //console.log(tile);
                        //}
                        this.moveTile(tile, positions.getFarthest());
                    }

                    if (!this.positionsEqual(cell, tile)) {
                        this.playerTurn = false;
                        //console.log('setting player turn to ', self.playerTurn);
                        moved = true; // The tile moved from its original cell!
                    }
                }
            }
        }

        //console.log('returning, playerturn is', self.playerTurn);
        //if (!moved) {
        //console.log('cell', cell);
        //console.log('tile', tile);
        //console.log('direction', direction);
        //console.log(this.toString());
        //}
        return new MoveResult(moved, score, won);
    }

    // 周边解冻
    private void unfreeze(Tile merged) {
        List<Tile> aroundSquare = getAroundSquare(merged.x, merged.y);

        if (CollUtil.isNotEmpty(aroundSquare)) {

            for (Tile obj : aroundSquare) {
                if (obj == null) {
                    continue;
                }
                Vec3StatusDTO vec3StatusDTO = findStatus(obj.status, 51);
                if (vec3StatusDTO != null) {
                    vec3StatusDTO.setCount(vec3StatusDTO.getCount() - 1);
                    if (vec3StatusDTO.getCount() <= 0) {
                        vec3StatusDTO.setStatus(0);
                        vec3StatusDTO.setCount(0);
                    }

                }
            }
        }
    }

    private Vec3StatusDTO findStatus(List<Vec3StatusDTO> status, int statusNum) {
        if (CollUtil.isEmpty(status)) {
            return null;
        }
        for (Vec3StatusDTO statusDTO : status) {
            if (statusDTO.getStatus() == statusNum) {
                return statusDTO;
            }
        }
        return null;
    }

    private List<Tile> getAroundSquare(int i, int j) {
        List<Tile> result = new ArrayList<>();
        if (isValidIndex(this.cells, i + 1, j)) {
            result.add(this.cells[i + 1][j]);
        }
        if (isValidIndex(this.cells, i - 1, j)) {
            result.add(this.cells[i - 1][j]);
        }
        if (isValidIndex(this.cells, i, j + 1)) {
            result.add(this.cells[i][j + 1]);
        }
        if (isValidIndex(this.cells, i, j - 1)) {
            result.add(this.cells[i][j - 1]);
        }
        return result;
    }

    private boolean isValidIndex(Tile[][] arr, int row, int col) {
        return arr != null && row >= 0 && row < arr.length && col >= 0 && col < arr[row].length;
    }

    public static boolean isContains(List<Vec3StatusDTO> statusDTOS, List<Integer> status) {
        if (CollUtil.isEmpty(statusDTOS)) {
            return false;
        }
        for (Vec3StatusDTO statusDTO : statusDTOS) {
            if (status.contains(statusDTO.getStatus())) {
                return true;
            }
        }
        return false;
    }

    public void prepareTiles() {
        for (int x = 0; x < SIZE; x++) {
            for (int y = 0; y < SIZE; y++) {
                Tile tile = this.cells[x][y];
                if (tile != null) {
                    tile.setMergedFrom(null);
                    tile.savePosition();
                }
            }
        }
    }

    // 对应 Grid.prototype.moveTile 方法
    public void moveTile(Tile tile, Vec3DTO cell) {
        this.cells[tile.x][tile.y] = null;
        this.cells[(int) cell.x][(int) cell.y] = tile;
        tile.updatePosition(cell);
    }

    public Traversals buildTraversals(Vec3DTO vector) {
        Traversals traversals = new Traversals();

        for (int pos = 0; pos < this.size; pos++) {
            traversals.x.add(pos);
            traversals.y.add(pos);
        }

        // Always traverse from the farthest cell in the chosen direction
        if (vector.x == 1) {
            Collections.reverse(traversals.x);
        }
        if (vector.getY() == 1) {
            Collections.reverse(traversals.y);
        }

        return traversals;
    }

    public boolean positionsEqual(Vec3DTO first, Tile second) {
        return first.x == second.x && first.y == second.y;
    }

    public boolean isWin() {
        return this.maxValue() >= 2048;
    }

    @FunctionalInterface
    interface CellCallback {
        void call(int x, int y, Tile tile);
    }

    public int islands() {
        // 内部递归标记方法
        class Mark {
            private Grid grid;

            public Mark(Grid grid) {
                this.grid = grid;
            }

            public void mark(int x, int y, int value) {
                if (x >= 0 && x < 4 && y >= 0 && y < 4 &&
                        grid.cells[x][y] != null &&
                        grid.cells[x][y].getValue() == value &&
                        !grid.cells[x][y].marked) {
                    grid.cells[x][y].marked = true;

                    for (int direction : new int[]{0, 1, 2, 3}) {
                        Vec3DTO vector = grid.getVector(direction);
                        mark((int) (x + vector.x), (int) (y + vector.y), value);
                    }
                }
            }
        }

        int islands = 0;

        // 初始化所有单元格的 marked 为 false
        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                if (this.cells[x][y] != null) {
                    this.cells[x][y].setMarked(false);
                }
            }
        }

        // 遍历所有单元格，查找未标记的单元格并标记
        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                if (this.cells[x][y] != null &&
                        !this.cells[x][y].isMarked()) {
                    islands++;
                    Mark markObj = new Mark(this);
                    markObj.mark(x, y, this.cells[x][y].getValue());
                }
            }
        }

        return islands;
    }

    /**
     * 根据方向获取对应的向量
     *
     * @param direction 方向
     * @return 对应的向量，如果不存在则返回 null
     */
    public Vec3DTO getVector(int direction) {
        // Vectors representing tile movement
        return this.vectors[direction];
    }

    // 生成道具


}
